﻿//  Copyright © 2009-2010 by Rhy A. Mednick
//  All rights reserved.
//  http://rhyduino.codeplex.com
//  
//  Redistribution and use in source and binary forms, with or without modification, 
//  are permitted provided that the following conditions are met:
//  
//  * Redistributions of source code must retain the above copyright notice, this list 
//    of conditions and the following disclaimer.
//  
//  * Redistributions in binary form must reproduce the above copyright notice, this 
//    list of conditions and the following disclaimer in the documentation and/or other 
//    materials provided with the distribution.
//  
//  * Neither the name of Rhy A. Mednick nor the names of its contributors may be used 
//    to endorse or promote products derived from this software without specific prior 
//    written permission.
//  
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
//  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
//  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
//  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
//  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
namespace Basic_Rhyduino_Sample
{
    using System;
    using System.Globalization;
    using System.Threading;
    using Rhyduino;
    using Rhyduino.Message;

    internal static class Program
    {
 
        #region Private Methods

        /// <summary>
        ///   Demonstrates how to auto-detect an attached Arduino and create 
        ///   a connection to it.
        /// </summary>
        private static string AutoDetectAndConnect()
        {
            // We're going to return the name of the detected port so that it 
            // could be used in additional demonstrations as part of this program.
            var detectedPort = String.Empty;

            try
            {
                // Creating the object with the default constructor causes
                // the program to look for an Arduino attached to a serial 
                // port. This will throw a NotSupportedException if none 
                // are found.

                // Always create the Arduino object in a using block so that 
                // it's disposed of when it goes out of scope.

                using (var arduino = new Arduino(""))
                {
                    detectedPort = arduino.PortName;
                    Console.WriteLine("Found Arduino at {0}.", detectedPort);
                    // Now open the connection between the PC and the Arduino.
                    // We don't need to specify what port to connect on because
                    // that's already been set (and can't be modified, by the way).
                    arduino.Connect();
                    // Just to show you how it's done, let's check to see if we're
                    // connected. The Connect() method is basically a thin wrapper
                    // for SerialPort.Open() so any exception that call might throw
                    // would bubble up to here. Basically, if we weren't connected
                    // then the following code wouldn't be executing.
                    Console.Write("Attempt to connect to Arduino was {0}.{1}",
                                  (arduino.IsConnected) ? "successful." : "unsuccessful.", Environment.NewLine);
                    // Now because the arduino variable is going out of scope, 
                    // the object will be disposed of, causing the connection to 
                    // be closed.
                }
            }
            catch (NotSupportedException nsex)
            {
                Console.WriteLine("Our attempt to identify a connected Arduino failed.{0}{1}",
                                  Environment.NewLine, nsex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("An unexpected exception was encountered.{0}{1}",
                                  Environment.NewLine, ex);

                // We don't know why this failed, so clear out the detected port variable
                // on the off chance that it had a value in it.
                detectedPort = String.Empty;
            }

            return detectedPort;
        }

        /// <summary>
        ///   Demonstrates how to configure and use a servo motor.
        /// </summary>
        /// <param name = "portName">The previously-discovered port name is reused.</param>
        private static void DemonstrateServo(string portName)
        {
            try
            {
                // Always create the Arduino object in a using block so that 
                // it's disposed of when it goes out of scope.

                // By setting the last parameter in the constructor to true, we're
                // asking the class to automatically connect to the specified Arduino.
                using (var arduino = new Arduino(portName, BAUD_RATE, true))
                {
                    // Make sure the Adruino is connected.
                    if (!arduino.IsConnected)
                    {
                        Console.WriteLine("The connection to the Arduino failed.");
                    }
                    else
                    {
                        Console.WriteLine("Connected to the Arduino successfully.");
                        // Construct and send the request to put pin 3 in servo mode.
                        const int pinNumber = 3;
                        var request = FirmataEncoder.BuildSetPinModeRequest(pinNumber,
                                                                            PinMode.Servo);
                        Console.WriteLine("Posting pin mode request.");
                        arduino.Post(request);

                        // Construct and send the the servo config message
                        request = FirmataEncoder.BuildServoConfigRequest(pinNumber, 400, 1500, 24);
                        Console.WriteLine("Posting servo config request.");
                        arduino.Post(request);

                        // Set servo to position 0, 90, 180, 90, 0
                        var positions = new[] {0, 90, 179, 90, 0};
                        foreach (var position in positions)
                        {
                            request = FirmataEncoder.BuildServoPositionRequest(pinNumber, position);
                            Console.WriteLine(
                                String.Format(CultureInfo.CurrentCulture, "Setting servo position to {0}.", position));
                            arduino.Post(request);
                            Thread.Sleep(1000); // sleep 1 sec
                        }
                        request = FirmataEncoder.BuildSetPinModeRequest(pinNumber, PinMode.Output);
                        Console.WriteLine("Disconnecting servo.");
                        arduino.Post(request);
                    }

                    // Now because the arduino variable is going out of scope, 
                    // the object will be disposed of, causing the connection to 
                    // be closed.
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("An unexpected exception was encountered.{0}{1}",
                                  Environment.NewLine, ex);
            }
        }

        private static void FlashAll()
        {
            var arduino = new Arduino("COM3");
            arduino.Connect();

            // Put pins into output mode.
            for (var i = 2; i <= 13; i++)
            {
                arduino.DigitalPins[i].SetPinMode(PinMode.Output);
            }
            for (var i = 2; i <= 13; i++)
            {
                Console.WriteLine(i);
                arduino.DigitalPins[i].SetPinValue(DigitalPinValue.High);
                //Thread.Sleep(1000);
                Console.ReadLine();
            }

            Console.ReadLine();
        }

        /// <summary>
        ///   Demonstrates how to change the value of a digital pin. This example 
        ///   flashes pin 13 which blinks the light on the Arduino board.
        /// </summary>
        /// <param name = "portName">The previously-discovered port name is reused.</param>
        private static void FlashDigitalPin13(string portName)
        {
            try
            {
                // Always create the Arduino object in a using block so that 
                // it's disposed of when it goes out of scope.

                // By setting the last parameter in the constructor to true, we're
                // asking the class to automatically connect to the specified Arduino.
                using (var arduino = new Arduino(portName, BAUD_RATE, true))
                {
                    Console.WriteLine("Connected to the Arduino successfully.");

                    // Now we create a message to set the pin mode for digital pin 13
                    // to output. Then post it.
                    arduino.Post(FirmataEncoder.BuildSetPinModeRequest(13, PinMode.Output));

                    // Now send pin 13 alternating high (1)/low (0) messages every quarter-second.
                    // Digital pins are arranged in ports so that several of them can be manipulated
                    // with a single message at a time.
                    // Pin 13 is in port 1, which represents pins 8-13. Write the value 1 and pin 
                    // 8 is set high. Write 1 << 5 to set pin 13 high.
                    for (var i = 0; i < 20; i++)
                    {
                        var value = (i % 2 == 0) ? 0x00 : (0x01 << 5);
                        arduino.Post(FirmataEncoder.BuildDigitalWriteRequest(1, value));
                        // Now wait for a quarter second
                        Thread.Sleep(250);
                    }
                    // Watch the pretty light blink on the arduino.
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("An unexpected exception was encountered.{0}{1}",
                                  Environment.NewLine, ex);
            }
        }

        private static void Main()
        {
            //ShowDemoIntro("-- AutoDetectAndConnect Demo --");
            //string arduinoPort = AutoDetectAndConnect();
            //WaitForEnter();

            //// We can't continue the demo unless we've detected the Arduino
            //// so bail if we don't have a stored port.
            //if (String.IsNullOrEmpty(arduinoPort)) return;

            //ShowDemoIntro("-- RequestFirmataProtocolVersion Demo --");
            //RequestFirmataProtocolVersion(arduinoPort);
            //WaitForEnter();

            //ShowDemoIntro("-- FlashDigitalPin13 Demo --");
            //FlashDigitalPin13(arduinoPort);
            //WaitForEnter();

            //ShowDemoIntro("-- ServoControl Demo --");
            //DemonstrateServo(arduinoPort);
            //WaitForEnter();

            FlashAll();
        }

        /// <summary>
        ///   Demonstrates how to request the Firmata version running on the 
        ///   connected Arduino.
        /// </summary>
        /// <param name = "portName">The previously-discovered port name is reused.</param>
        private static void RequestFirmataProtocolVersion(string portName)
        {
            try
            {
                // Always create the Arduino object in a using block so that 
                // it's disposed of when it goes out of scope.

                // By setting the last parameter in the constructor to true, we're
                // asking the class to automatically connect to the specified Arduino.
                using (var arduino = new Arduino(portName, BAUD_RATE, true))
                {
                    // Make sure the Adruino is connected.
                    if (!arduino.IsConnected)
                    {
                        Console.WriteLine("The connection to the Arduino failed.");
                    }
                    else
                    {
                        Console.WriteLine("Connected to the Arduino successfully.");
                        // Connect the event handler
                        arduino.FirmataVersionReportReceived +=
                            (sender, message) =>
                            Console.WriteLine("Firmata Version Received: {0}.{1}.",
                                              message.Data.MajorVersion,
                                              message.Data.MinorVersion);

                        // Construct and send the request for a Firmata protocol version report.
                        var request = FirmataEncoder.BuildProtocolVersionRequest();

                        Console.WriteLine("Posting Firmata protocol version request.");
                        // Post the request 
                        arduino.Post(request);


                        // Now wait second for a response. It won't take that long, 
                        // but I'd like to just be safe. I'd like you to see the event handler
                        // get fired.
                        Console.WriteLine("Waiting for a response.");
                        Thread.Sleep(TimeSpan.FromSeconds(1));
                    }
                    // Now because the arduino variable is going out of scope, 
                    // the object will be disposed of, causing the connection to 
                    // be closed.
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("An unexpected exception was encountered.{0}{1}",
                                  Environment.NewLine, ex);
            }
        }

        #endregion

        #region Demo Helper Methods - not part of demo

        private static void ShowDemoIntro(string intro)
        {
            Console.WriteLine("{0}{1}{0}", Environment.NewLine, intro);
        }

        private static void WaitForEnter()
        {
            Console.WriteLine("Demo complete. Press <enter> to continue.");
            Console.ReadLine();
        }

        #endregion

        #region Private Constants and Static Fields

        private const int BAUD_RATE = 57600;

        #endregion
    }
}
